import { ICancelable } from "../../tsgf/ICancelable";
import { IResult } from "../../tsgf/Result";
import { EFrameSyncState, ERoomCreateType, ICreateRoomPara, IRoomInfo, IRoomRegInfo, ITeamInfo } from "../../tsgf/room/IRoomInfo";
import { IRedisClient } from "../redisHelper";
import { buildGuid } from "../ServerUtils";
import { IRoomRegInfoInServer } from "./Models";


/**房间注册信息和房间信息的结合体*/
export interface IRoomInfoPack {
    regInfo: IRoomRegInfoInServer;
    roomInfo: IRoomInfo;
}
/*
export interface IRoomRegChanged {
    regInfo: IRoomGameServerRegInfo;
    isCreateOrChange: boolean;
    isDelete: boolean;
}
*/
export enum ERoomRegChangedType {
    Create = 1,
    Delete = 2,
    PlayerJoinRoom = 3,
    PlayerLeaveRoom = 4,
    PlayerChangeTeam = 5,
}
export interface IRoomRegChangedBase {
    regInfo: IRoomRegInfoInServer;
}
export interface IRoomRegCreate extends IRoomRegChangedBase {
    changedType: ERoomRegChangedType.Create;
}
export interface IRoomRegDelete extends IRoomRegChangedBase {
    changedType: ERoomRegChangedType.Delete;
}
export interface IRoomRegPlayerJoinRoom extends IRoomRegChangedBase {
    changedType: ERoomRegChangedType.PlayerJoinRoom;
    joinRoomPlayerId: string;
    teamId?: string;
}
export interface IRoomRegPlayerLeaveRoom extends IRoomRegChangedBase {
    changedType: ERoomRegChangedType.PlayerLeaveRoom;
    leaveRoomPlayerId: string;
    teamId?: string;
}
export interface IRoomRegPlayerChangeTeam extends IRoomRegChangedBase {
    changedType: ERoomRegChangedType.PlayerChangeTeam;
    changePlayerId: string;
    oldTeamId?: string;
    newTeamId?: string;
}
export type IRoomRegChanged = IRoomRegCreate | IRoomRegDelete
    | IRoomRegPlayerJoinRoom | IRoomRegPlayerLeaveRoom | IRoomRegPlayerChangeTeam;

/**房间公共操作（跨服务器）*/
export class RoomHelper {

    private static getRedisClient: (reuseClient: boolean) => Promise<IRedisClient>;
    private static oneDaySec = 24 * 60 * 60;
    private static oneHourSec = 60 * 60;

    public static init(getRedisClient: (reuseClient: boolean) => Promise<IRedisClient>) {
        RoomHelper.getRedisClient = getRedisClient;
    }

    public static buildRegRoomRedisKey(roomId: string) {
        return `Rooms:Reg:roomId_${roomId}`;
    }
    public static buildCreateRoomInfoRedisKey(roomId: string) {
        return `Rooms:CreateInfo:roomId_${roomId}`;
    }


    /**
     * 构建创建房间的相关信息（房间注册信息和初始的房间信息）
     *
     * @public
     * @param appId
     * @param gameServerNodeId
     * @param para
     * @returns
     */
    public static buildRoomInfo(appId: string, gameServerNodeId: string, para: ICreateRoomPara)
        : IRoomInfoPack {
        let roomId = buildGuid('Room_');

        let fixedTeamList: ITeamInfo[] | null = null;
        if (para.fixedTeamInfoList && para.fixedTeamInfoList.length) {
            //指定的队伍信息列表
            fixedTeamList = para.fixedTeamInfoList;
        } else if (para.fixedTeamCount) {
            //指定固定的队伍数量来生成
            fixedTeamList = [];
            for (let i = 0; i < para.fixedTeamCount; i++) {
                let id = (i + 1).toString();
                fixedTeamList.push({
                    id: (i + 1).toString(),
                    name: '队伍' + id,
                    minPlayers: para.fixedTeamMinPlayers ?? 1,
                    maxPlayers: para.fixedTeamMaxPlayers ?? para.maxPlayers,
                });
            }
        }


        return {
            regInfo: {
                roomId: roomId,
                appId: appId,
                ownerPlayerId: para.ownerPlayerId,
                gameServerNodeId: gameServerNodeId,
                createTime: Date.now(),
                teamsPlayerIds: [],
            },
            roomInfo: {
                roomId: roomId,
                roomName: para.roomName,

                roomType: para.roomType,
                createType: ERoomCreateType.COMMON_CREATE,
                maxPlayers: para.maxPlayers,
                customProperties: para.customProperties,
                ownerPlayerId: para.ownerPlayerId,
                isPrivate: para.isPrivate,
                matcherKey: para.matcherKey,
                isForbidJoin: false,

                playerList: [],
                teamList: fixedTeamList ?? [],
                fixedTeamCount: fixedTeamList?.length,
                freeTeamMinPlayers: para.freeTeamMinPlayers,
                freeTeamMaxPlayers: para.freeTeamMaxPlayers,

                frameRate: 30,
                frameSyncState: EFrameSyncState.STOP,
                createTime: Date.now(),
                startGameTime: 0,
            },
        };
    }

    /**
     * 注册服务器房间信息，注册完后，玩家才可以连接到对应游戏服务器进入房间
     *
     * @public
     * @param roomRegInfo 房间的注册信息，会在redis里保留1天，即房间1天内都可以加人等操作
     * @param roomInfo 创建时的房间信息，被游戏服务器拉取后就删除了
     */
    public static async regRoom(roomRegInfo: IRoomRegInfoInServer, roomInfo: IRoomInfo) {
        let regKey = RoomHelper.buildRegRoomRedisKey(roomRegInfo.roomId);
        let roomKey = RoomHelper.buildCreateRoomInfoRedisKey(roomRegInfo.roomId);
        let client = await RoomHelper.getRedisClient(true);
        await client.setObject(regKey, roomRegInfo, RoomHelper.oneDaySec);
        await client.setObject(roomKey, roomInfo, RoomHelper.oneHourSec);
        client.publishObject('RoomRegInfoChanged', {
            changedType: ERoomRegChangedType.Create,
            regInfo: roomRegInfo,
        } as IRoomRegChanged);
    }
    /**
     * 玩家进入房间造成的更新房间注册信息
     *
     * @public
     * @param roomRegInfo 房间的注册信息，会在redis里保留1天，即房间1天内都可以加人等操作
     */
    public static async updateRoomRegInfoFromJoinPlayer(roomRegInfo: IRoomRegInfoInServer,
        joinRoomPlayerId: string, teamId?: string) {
        let regKey = RoomHelper.buildRegRoomRedisKey(roomRegInfo.roomId);
        let client = await RoomHelper.getRedisClient(true);
        await client.setObject(regKey, roomRegInfo, RoomHelper.oneDaySec);
        client.publishObject('RoomRegInfoChanged', {
            changedType: ERoomRegChangedType.PlayerJoinRoom,
            regInfo: roomRegInfo,
            joinRoomPlayerId: joinRoomPlayerId,
            teamId: teamId,
        } as IRoomRegChanged);
    }
    /**
     * 玩家离开房间造成的更新房间注册信息
     *
     * @public
     * @param roomRegInfo 房间的注册信息，会在redis里保留1天，即房间1天内都可以加人等操作
     */
    public static async updateRoomRegInfoFromLeavePlayer(roomRegInfo: IRoomRegInfoInServer,
        leaveRoomPlayerId: string, teamId?: string) {
        let regKey = RoomHelper.buildRegRoomRedisKey(roomRegInfo.roomId);
        let client = await RoomHelper.getRedisClient(true);
        await client.setObject(regKey, roomRegInfo, RoomHelper.oneDaySec);
        client.publishObject('RoomRegInfoChanged', {
            changedType: ERoomRegChangedType.PlayerLeaveRoom,
            regInfo: roomRegInfo,
            leaveRoomPlayerId: leaveRoomPlayerId,
            teamId: teamId,
        } as IRoomRegChanged);
    }
    /**
     * 玩家变更队伍造成的更新房间注册信息
     *
     * @public
     * @param roomRegInfo 房间的注册信息，会在redis里保留1天，即房间1天内都可以加人等操作
     * @param oldTeamId 变更前所在队伍ID
     * @param newTeamId 变更后所在队伍ID
     */
    public static async updateRoomRegInfoFromChangePlayerTeam(roomRegInfo: IRoomRegInfoInServer,
        changePlayerId: string, oldTeamId?: string, newTeamId?: string) {
        let regKey = RoomHelper.buildRegRoomRedisKey(roomRegInfo.roomId);
        let client = await RoomHelper.getRedisClient(true);
        await client.setObject(regKey, roomRegInfo, RoomHelper.oneDaySec);
        client.publishObject('RoomRegInfoChanged', {
            changedType: ERoomRegChangedType.PlayerChangeTeam,
            regInfo: roomRegInfo,
            changePlayerId: changePlayerId,
            oldTeamId: oldTeamId,
            newTeamId: newTeamId,
        } as IRoomRegChanged);
    }

    /**
     * 查询房间注册信息
     *
     * @public
     * @param roomId
     * @returns
     */
    public static async getRoomRegInfo(roomId: string): Promise<IRoomRegInfoInServer | null> {
        let regKey = RoomHelper.buildRegRoomRedisKey(roomId);
        let client = await RoomHelper.getRedisClient(true);
        let regInfo = await client.getObject<IRoomRegInfoInServer>(regKey);
        return regInfo;
    }

    /**
     * [游戏服务器]提取已经注册的房间信息, 提取完后不可再次提取
     *
     * @public
     * @param roomId
     * @param gameServerNodeId 传入当前游戏服务器节点ID，验证房间是否分配给这个节点的，没通过返回null
     * @returns
     */
    public static async extractRoomInfo(roomId: string, gameServerNodeId: string)
        : Promise<{ roomInfo: IRoomInfo, regInfo: IRoomRegInfoInServer } | null> {
        let regKey = RoomHelper.buildRegRoomRedisKey(roomId);
        let roomKey = RoomHelper.buildCreateRoomInfoRedisKey(roomId);
        let client = await RoomHelper.getRedisClient(true);
        let regInfo = await client.getObject<IRoomRegInfoInServer>(regKey);
        if (!regInfo || regInfo.gameServerNodeId != gameServerNodeId) {
            //没注册信息或者不是给这个游戏服务器ID的, 返回null
            return null;
        }
        let roomInfo = await client.getObject<IRoomInfo>(roomKey);
        if (!roomInfo) {
            return null;
        }
        client.delete(roomKey);
        return {
            roomInfo,
            regInfo,
        };
    }


    /**
     * 删除房间注册信息(只删除redis里保存的信息，让大厅无法获取到信息，游戏服务器部分需要自己处理，所以本方法一般由房间所属游戏服务器调用)
     *
     * @public
     * @param roomId
     * @returns
     */
    public static async deleteRoomRegInfo(roomId: string): Promise<void> {
        let client = await RoomHelper.getRedisClient(true);
        let regKey = RoomHelper.buildRegRoomRedisKey(roomId);
        let regInfo = await client.getObject<IRoomRegInfoInServer>(regKey);
        let roomKey = RoomHelper.buildCreateRoomInfoRedisKey(roomId);
        await client.delete(regKey, roomKey);
        if (regInfo) {
            //有注册信息才推送通知!
            client.publishObject('RoomRegInfoChanged', {
                changedType: ERoomRegChangedType.Delete,
                regInfo: regInfo,
            } as IRoomRegChanged);
        }
    }


    /**
     * 开始侦听全局的房间注册信息变更事件(跨服务器), 返回取消侦听的对象,需要自行保存!
     *
     * @public
     * @param listen
     * @returns
     */
    public static async startListenRoomRegInfoChanged(listen: (changedInfo: IRoomRegChanged) => void): Promise<ICancelable> {
        let subscribeClient = await this.getRedisClient(false);
        await subscribeClient.subscribeObject<IRoomRegChanged>("RoomRegInfoChanged", listen);
        return {
            async cancel(): Promise<void> {
                await subscribeClient.disconnect();
            }
        };
    }

}